﻿using System.Collections.Generic;
using System.Text.RegularExpressions;
using JetBlack.Diagnostics;
using JetBlack.Text;
using JetBlack.TopicBus.Messages;

namespace JetBlack.TopicBus.Distributor
{
    internal class SubscriptionCache : Dictionary<string, List<Interactor>> { }

    internal class SubscriptionManager
    {
        internal delegate void OnSubscriptionHandler(ForwardedSubscriptionRequest message);
        internal event OnSubscriptionHandler OnSubscription;

        internal delegate void OnClientMessageHandler(Interactor publisher, Interactor subscriber, ClientMessage message);
        internal event OnClientMessageHandler OnClientMessage;

        internal delegate void OnSubscriberMessageHandler(Interactor publisher, Interactor subscriber, SubscriberMessage message);
        internal event OnSubscriberMessageHandler OnSubscriberMessage;

        internal delegate void OnStaleTopicHandler(Interactor publisher, Interactor subscriber, string topic);
        internal event OnStaleTopicHandler OnStaleTopic;

        private readonly SubscriptionCache subscriptionCache = new SubscriptionCache();

        internal void HandleClientMessage(ClientMessage message, Interactor publisher)
        {
            if (subscriptionCache.ContainsKey(message.Topic) && OnClientMessage != null)
            {
                foreach (Interactor subscriber in subscriptionCache[message.Topic])
                    if (message.ClientId == subscriber.Id)
                        OnClientMessage(publisher, subscriber, message);
            }
        }

        internal void HandleSubscriberMessage(SubscriberMessage message, Interactor publisher)
        {
            if (subscriptionCache.ContainsKey(message.Topic) && OnSubscriberMessage != null)
            {
                foreach (Interactor subscriber in subscriptionCache[message.Topic])
                    OnSubscriberMessage(publisher, subscriber, message);
            }
        }

        internal void HandleSubscription(SubscriptionRequest message, Interactor subscriber)
        {
            AppEnvironment.TraceSource.Verbose("Received subscription from {0} on \"{1}\"", subscriber, message);

            if (message.IsAdd)
                AddSubscription(message.Topic, subscriber);
            else
                RemoveSubscription(message.Topic, subscriber);
        }

        private void AddSubscription(string topic, Interactor subscriber)
        {
            // Prepare the topic/field/value area
            if (!subscriptionCache.ContainsKey(topic))
                subscriptionCache.Add(topic, new List<Interactor>());

            // Find the list of interactors that have subscribed to this topic.
            List<Interactor> subscribers = subscriptionCache[topic];
            if (!subscribers.Contains(subscriber))
                subscribers.Add(subscriber);

            // Inform those waiting for this request
            NotifyListeners(subscriber.Id, topic, true);
        }

        private void RemoveSubscription(string topic, Interactor subscriber)
        {
            List<Interactor> subscribers;
            if (!subscriptionCache.TryGetValue(topic, out subscribers))
                return;

            if (subscribers.Contains(subscriber))
                subscribers.Remove(subscriber);

            if (subscribers.Count == 0)
                subscriptionCache.Remove(topic);

            // Inform those waiting for this request
            NotifyListeners(subscriber.Id, topic, false);
        }

        internal void Remove(Interactor subscriber)
        {
            AppEnvironment.TraceSource.Verbose("Removing subscriptions for {0}", subscriber);

            // Remove the subscriptions
            List<string> emptySubscriptions = new List<string>();
            foreach (KeyValuePair<string, List<Interactor>> item in subscriptionCache)
            {
                if (item.Value.Contains(subscriber))
                {
                    item.Value.Remove(subscriber);
                    if (item.Value.Count == 0)
                        emptySubscriptions.Add(item.Key);
                    NotifyListeners(subscriber.Id, item.Key, false);
                }
            }

            foreach (string topic in emptySubscriptions)
                subscriptionCache.Remove(topic);
        }

        private void NotifyListeners(int clientId, string topic, bool isAdd)
        {
            if (OnSubscription != null)
                OnSubscription(new ForwardedSubscriptionRequest(clientId, topic, isAdd));
        }

        internal void HandleNewNotificationRequest(Regex topicRegex, Interactor notifiable)
        {
            foreach (KeyValuePair<string, List<Interactor>> item in subscriptionCache)
            {
                if (topicRegex.Match(item.Key).Success)
                {
                    AppEnvironment.TraceSource.Verbose("Notification pattern {0} matched [{1}] subscribers", topicRegex, item.Value.ToDelimitedString(", ", x => x.ToString()));

                    foreach (Interactor subscriber in item.Value)
                        notifiable.SendMessage(new ForwardedSubscriptionRequest(subscriber.Id, item.Key, true));
                }
            }
        }

        internal void HandleStalePublisher(Interactor publisher, IEnumerable<string> staleTopics)
        {
            foreach (string staleTopicName in staleTopics)
            {
                if (subscriptionCache.ContainsKey(staleTopicName))
                {
                    foreach (Interactor subscriber in subscriptionCache[staleTopicName])
                        OnStaleTopic(publisher, subscriber, staleTopicName);
                }
            }
        }
    }
}
